%%%-------------------------------------------------------------------
%%% File    : dtl_server.erl
%%% Author  : Liu Yubao <yubao.liu@gmail.com>
%%% Description : A server to compile Django template files
%%% Created : 28 Jan 2009 by  Liu Yubao <yubao.liu@gmail.com>
%%%
%%% Copyright (c) 2009, Liu Yubao.  All rights reserved.
%%%
%%% Redistribution and use in source and binary forms, with or without
%%% modification, are permitted provided that the following conditions
%%% are met:
%%%
%%%   * Redistributions of source code must retain the above copyright
%%%     notice, this list of conditions and the following disclaimer.
%%%
%%%   * Redistributions in binary form must reproduce the above
%%%     copyright notice, this list of conditions and the following
%%%     disclaimer in the documentation and/or other materials
%%%     provided with the distribution.
%%%
%%% THIS  SOFTWARE   IS  PROVIDED   BY  THE  COPYRIGHT   HOLDERS  AND
%%% CONTRIBUTORS  "AS  IS" AND  ANY  EXPRESS  OR IMPLIED  WARRANTIES,
%%% INCLUDING,  BUT  NOT  LIMITED   TO,  THE  IMPLIED  WARRANTIES  OF
%%% MERCHANTABILITY  AND   FITNESS  FOR  A   PARTICULAR  PURPOSE  ARE
%%% DISCLAIMED.   IN   NO  EVENT  SHALL  THE   COPYRIGHT  HOLDERS  OR
%%% CONTRIBUTORS  BE  LIABLE FOR  ANY  DIRECT, INDIRECT,  INCIDENTAL,
%%% SPECIAL, EXEMPLARY, OR  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
%%% LIMITED TO, PROCUREMENT OF  SUBSTITUTE GOODS OR SERVICES; LOSS OF
%%% USE, DATA,  OR PROFITS; OR BUSINESS  INTERRUPTION) HOWEVER CAUSED
%%% AND  ON ANY  THEORY  OF LIABILITY,  WHETHER  IN CONTRACT,  STRICT
%%% LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
%%% ANY WAY OUT  OF THE USE OF THIS SOFTWARE, EVEN  IF ADVISED OF THE
%%% POSSIBILITY OF SUCH DAMAGE.
%%%-------------------------------------------------------------------
-module(dtl_server).
-author('yubao.liu@gmail.com').

-behaviour(gen_server).

%% API
-export([start_link/0, start/0, stop/0, lookup/1, make/0]).

%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
	 terminate/2, code_change/3]).

-define(SERVER, ?MODULE).

-record(state, {id}).
-record(modinfo, {filepath, modname, mtime, source, dependencies}).

%%====================================================================
%% API
%%====================================================================

lookup(FilePath) ->
    case ets:lookup(?MODULE, FilePath) of
        [ModInfo] ->
            ModInfo#modinfo.modname;
        [] ->
            {ok, Module} = gen_server:call(?MODULE, {compile, FilePath}),
            Module
    end.

make() ->
    gen_server:call(?MODULE, make).

start() ->
    start_link().

stop() ->
    gen_server:call(?MODULE, stop).

%%--------------------------------------------------------------------
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
%% Description: Starts the server
%%--------------------------------------------------------------------
start_link() ->
    gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).

%%====================================================================
%% gen_server callbacks
%%====================================================================

%%--------------------------------------------------------------------
%% Function: init(Args) -> {ok, State} |
%%                         {ok, State, Timeout} |
%%                         ignore               |
%%                         {stop, Reason}
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([]) ->
    ets:new(?MODULE, [set, protected, named_table, {keypos,2}]),
    {ok, #state{id=1}}.

%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
%%                                      {reply, Reply, State, Timeout} |
%%                                      {noreply, State} |
%%                                      {noreply, State, Timeout} |
%%                                      {stop, Reason, Reply, State} |
%%                                      {stop, Reason, State}
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call({compile, FilePath}, _From, State) ->
    #state{id=Id} = State,
    Time = filelib:last_modified(FilePath),
    case ets:lookup(?MODULE, FilePath) of
        [ModInfo] ->
            Module = ModInfo#modinfo.modname,
            Id2 = Id;
        [] ->
	    Name = lists:flatten(io_lib:format("dtl_m_~b", [State#state.id])),
            Module = list_to_atom(Name),
            Id2 = Id + 1
    end,
    case erlydtl:compile(FilePath, Module) of
	ok ->
	    ets:insert(?MODULE, #modinfo{filepath=FilePath, modname=Module,
					 mtime=Time, source=Module:source(),
					 dependencies=Module:dependencies()}),
	    {reply, {ok, Module}, State#state{id=Id2}};
	Err ->
	    {reply, Err, State#state{id=Id2}}
    end;

handle_call(make, _From, State) ->
    Result = lists:foldl(fun make_one_template/2, [], ets:match(?MODULE, '$1')),
    {reply, {ok, Result}, State}.

make_one_template([ModInfo], Acc) ->
    #modinfo{filepath=FilePath,
	     modname=Module,
	     source=Source,
	     dependencies=Dependencies} = ModInfo,
    Time = filelib:last_modified(FilePath),
    case erlydtl:compile(FilePath, Module) of
	ok ->
	    Source2 = Module:source(),
	    Dependencies2 = Module:dependencies(),
	    case {Source2, Dependencies2} of
		{Source, Dependencies} ->
		    Msg = "up to date";
		_ ->
		    Msg = "recompiled"
	    end,
	    ets:insert(?MODULE, ModInfo#modinfo{
				  mtime=Time,
				  source=Source2,
				  dependencies=Dependencies2});
	{error, Msg} ->
	    void
    end,
    [{FilePath, Module, Time, Msg} | Acc].

%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
%%                                      {noreply, State, Timeout} |
%%                                      {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast(_Msg, State) ->
    {noreply, State}.

%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
%%                                       {noreply, State, Timeout} |
%%                                       {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info(_Info, State) ->
    {noreply, State}.

%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any necessary
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, _State) ->
    ets:delete(?MODULE),
    ok.

%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
